package pokestar;

import comunicacao.*;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import objectos.*;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;

/**
 * @author igo
 * 
 * Classe PokerStar
 * Classe principal do Servidor.
 */
public class PokeStar {

    /**
     * A Classe Ordem é a responsavel pelo movimento da Personagem, seja esta um "mover" para um local vazio ou um "seguir" uma qualquer Personagem.
     */
    private class Ordem extends Thread {

        private boolean update;
        private boolean seguir;
        private Objecto clicado;
        private Point P_Actual;
        private Point P_Destino;
        private Objecto O;
        private Jogo J;
        private Jogador j;

        /**
         * Constructor da Ordem.
         * 
         * @param clicado - Objecto (Personagem ou Vazio) onde o jogador clica com o botão direito.
         * @param j - Jogador que faz a acção.
         * @param pactual - É o ponto onde a Personagem está no momento.
         * @param p - É o ponto de destino da ordem
         * @param s - Personagem sob a qual a ordem será executada.
         * @param J - Jogo onde a ordem será executada.
         */
        private Ordem(Objecto clicado, Jogador j, Point pactual, Point p, Objecto s, Jogo J) {
            this.clicado = clicado;
            this.P_Actual = pactual;
            this.P_Destino = p;
            this.O = s;
            this.J = J;
            this.j = j;
            if (clicado.getTipo() == TipoObjecto.VAZIO) {
                seguir = false;
            } else if (clicado.getTipo() == TipoObjecto.POKEMON) {
                seguir = true;
            } else if (clicado.getTipo() == TipoObjecto.TREINADOR) {
                seguir = true;
            }
        }

        /**
         * 
         * @return Retorna o Objecto sob a qual a ordem é executada
         */
        public Objecto getObjecto() {
            return O;
        }

        /**
         * Método que altera o Objecto onde é clicado com o botão direito.
         * @param o - Objecto clicado com o botão direito.
         */
        public void setClicado(Objecto o) {
            this.clicado = o;
            if (clicado.getTipo() == TipoObjecto.VAZIO) {
                seguir = false;
            } else if (clicado.getTipo() == TipoObjecto.POKEMON) {
                seguir = true;
            } else if (clicado.getTipo() == TipoObjecto.TREINADOR) {
                seguir = true;
            }
        }

        /**
         * 
         * @return Retorna o Ponto Actual da Personagem
         */
        public Point getPActual() {
            return P_Actual;
        }

        /**
         * Método que altera o Ponto de Destino da Ordem
         * @param d - Recebe um Ponto
         */
        public void setPDestino(Point d) {
            this.P_Destino = d;
        }

        /**
         * Método que executa a ordem
         * Faz a Personagem mover-se na vertica (X) e na horiontal (Y) até que chegue ao ponto de destino
         * 
         */
        public synchronized void run() {
            update = false;
            for (int o = 0; o < J.getOrdens().size(); o++) {
                if (J.getOrdens().get(o).getObjecto() == this.O) {
                    update = true;
                    J.getOrdens().get(o).setClicado(clicado);
                    J.getOrdens().get(o).setPDestino(P_Destino);
                }
            }

            if (!update) {

                J.getOrdens().add(this);
                System.out.println("Lista Inicio: " + J.getOrdens());
                int espera = 1000;
                if (O.getTipo() == TipoObjecto.TREINADOR) {
                    espera = espera / ((Treinador) O).getVelocidade();
                } else if (O.getTipo() == TipoObjecto.POKEMON) {
                    espera = espera / ((Pokemon) O).getVelocidade();
                }

                try {
                    while (!interrupted()) {
                        Point NP = P_Actual;
                        if (P_Actual.getX() < P_Destino.getX()) {
                            NP = new Point((int) (P_Actual.getX() + 1), (int) P_Actual.getY());

                            if (J.getMapa().getMatriz()[(int) NP.getX()][(int) NP.getY()].getTipo() == TipoObjecto.VAZIO) {
                                J.getMapa().getMatriz()[(int) P_Actual.getX()][(int) P_Actual.getY()] = new Vazio();
                                J.getMapa().getMatriz()[(int) NP.getX()][(int) P_Actual.getY()] = O;
                                P_Actual = NP;
                                sleep(espera);
                            }
                        } else if (P_Actual.getX() > P_Destino.getX()) {
                            NP = new Point((int) (P_Actual.getX() - 1), (int) P_Actual.getY());
                            if (J.getMapa().getMatriz()[(int) NP.getX()][(int) NP.getY()].getTipo() == TipoObjecto.VAZIO) {
                                J.getMapa().getMatriz()[(int) P_Actual.getX()][(int) P_Actual.getY()] = new Vazio();
                                J.getMapa().getMatriz()[(int) NP.getX()][(int) P_Actual.getY()] = O;
                                P_Actual = NP;
                                sleep(espera);
                            }
                        }

                        if (P_Actual.getY() < P_Destino.getY()) {
                            NP = new Point((int) P_Actual.getX(), (int) (P_Actual.getY() + 1));
                            if (J.getMapa().getMatriz()[(int) NP.getX()][(int) NP.getY()].getTipo() == TipoObjecto.VAZIO) {
                                J.getMapa().getMatriz()[(int) P_Actual.getX()][(int) P_Actual.getY()] = new Vazio();
                                J.getMapa().getMatriz()[(int) P_Actual.getX()][(int) NP.getY()] = O;
                                P_Actual = NP;
                                sleep(espera);
                            }
                        } else if (P_Actual.getY() > P_Destino.getY()) {
                            NP = new Point((int) P_Actual.getX(), (int) (P_Actual.getY() - 1));
                            if (J.getMapa().getMatriz()[(int) NP.getX()][(int) NP.getY()].getTipo() == TipoObjecto.VAZIO) {
                                J.getMapa().getMatriz()[(int) P_Actual.getX()][(int) P_Actual.getY()] = new Vazio();
                                J.getMapa().getMatriz()[(int) P_Actual.getX()][(int) NP.getY()] = O;
                                P_Actual = NP;
                                sleep(espera);
                            }
                        }

                        if (seguir) {
                            boolean segue = true;
                            if (clicado.getTipo() == TipoObjecto.POKEMON) {
                                segue = ((Pokemon) clicado).estaVivo();
                            } else if (clicado.getTipo() == TipoObjecto.TREINADOR) {
                                segue = ((Treinador) clicado).estaVivo();
                            }

                            if (!segue) {
                                this.interrupt();
                            }

                            for (int l = 0; l < J.getMapa().getMatriz().length; l++) {
                                for (int c = 0; c < J.getMapa().getMatriz()[l].length; c++) {
                                    if (J.getMapa().getMatriz()[l][c] == clicado) {
                                        P_Destino = new Point(l, c);
                                    }
                                }
                            }
                        }
                        if ((P_Actual.getX() == P_Destino.getX() && P_Actual.getY() == P_Destino.getY())) {
                            this.interrupt();
                        }
                    }
                    j.setSelect(null);
                    J.getOrdens().remove(this);
                    System.out.println("Lista Fim:" + J.getOrdens());
                } catch (InterruptedException ex) {
                    //Logger.getLogger(PokeStar.class.getName()).log(Level.SEVERE, null, ex);
                    J.getOrdens().remove(this);
                    System.out.println("Ordem Interrompida");
                }
            }
        }
    }

    /**
     * A classe Jogo é a classe que gere um Jogo.
     * Faz o envio dos objectos (mapa, chat, infos) e recebe os inputs (cliques) para os/dos jogadores inseridos no Jogo.
     * Verifica também quando é que o jogo acaba, transmitindo uma mensagem de vitória antes do clientes se desconectar do Jogo.
     */
    private class Jogo extends Thread {

        private String Nome;
        private int J_max;
        private Jogador Criador;
        private LinkedList<Jogador> jogadores = new LinkedList<Jogador>();
        private Mapa m;
        private boolean ligado = false;
        private LinkedList<Ordem> lista_ordens = new LinkedList<Ordem>();
        private LinkedList<Ataque> lista_ataques = new LinkedList<Ataque>();
        private int actualizacao = 400; // 1000 - 1 fps | 250 - 4fps | 34 - 30fps
        private LinkedList<Treinador> lt = new LinkedList<Treinador>();

        /**
         * Constructor do Jogo
         * 
         * @param CJ - Recebe um tipo de mensagem "CriarJogo" que tem todas as informações para dar início ao Jogo.
         */
        public Jogo(CriarJogo CJ) {
            this.Nome = CJ.getNome();
            this.J_max = CJ.getJMax();

            if (CJ.getP2IA()) {
                Jogador J = new Jogador("Player2", "Easy");
                this.addJogador(J);
            }
            if (CJ.getP3IA()) {
                Jogador J = new Jogador("Player3", "Easy");
                this.addJogador(J);
            }
            if (CJ.getP4IA()) {
                Jogador J = new Jogador("Player4", "Easy");
                this.addJogador(J);
            }
        }

        /**
         * 
         * @return Retorna a lista de jogadores inseridos no Jogo.
         */
        public LinkedList<Jogador> getJogadores() {
            return jogadores;
        }

        /**
         * 
         * @return Retorna o estado do Jogo, true se o jogo está a ser jogado ou false caso o jogo já tenha terminado.
         */
        public boolean getStatus() {
            return ligado;
        }

        /**
         * 
         * @return Retorna o nome do Jogo.
         */
        public String getNome() {
            return Nome;
        }

        /**
         * 
         * @return Retorna o mapa do Jogo.
         */
        public Mapa getMapa() {
            return m;
        }

        /**
         * 
         * @return Retorna o número máximo de jogadores que o Jogo pode ter.
         */
        public int getJMax() {
            return J_max;
        }

        /**
         * 
         * @return Retorna o Criador do Jogo
         */
        public Jogador getCriador() {
            return Criador;
        }

        /**
         * Método que torna um Jogador no Criador do Jogo.
         * 
         * @param J - Recebe um Jogador
         */
        public void setCriador(Jogador J) {
            this.Criador = J;
            this.addJogador(Criador);
        }

        /**
         * Método que irá remover um jogador da lista de jogadores do Jogo.
         * 
         * @param J - Recebe um jogador
         */
        private void rmvJogador(Jogador J) {
            jogadores.remove(J);
        }

        /**
         * Método que adiciona um Jogador à lista de jogadores do Jogo.
         * 
         * @param J - Recebe um Jogador
         */
        public void addJogador(Jogador J) {
            jogadores.add(J);
            if (this.cheio()) {
                for (int j = 0; j < jogadores.size(); j++) {
                    if (jogadores.get(j).eHumano()) {
                        try {
                            Confirmacao C = new Confirmacao(true);
                            jogadores.get(j).getOut().reset();
                            jogadores.get(j).getOut().writeObject(C);
                            jogadores.get(j).getOut().flush();
                        } catch (IOException ex) {
                            Logger.getLogger(PokeStar.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
                if (this.cheio()) {
                    this.start();
                }
            }
        }

        /**
         * 
         * @return Retorna true caso os lugares para jogar o jogo já estejam todos preenchidos ou false caso ainda haja lugares livres.
         */
        private boolean cheio() {
            if (jogadores.size() == J_max) {
                return true;
            }
            return false;
        }

        /**
         * 
         * @return Retorna a lista de ordens do jogo.
         */
        private synchronized LinkedList<Ordem> getOrdens() {
            return lista_ordens;
        }

        /**
         * Método Fim termina o jogo, interrompendo e apagando as ordens em execução e as threads de ataque que cada personagem tem.
         */
        private void Fim() {
            for (int o = 0; o < lista_ordens.size(); o++) {
                try {
                    lista_ordens.get(o).interrupt();
                    lista_ordens.get(o).join();
                } catch (InterruptedException ex) {
                    Logger.getLogger(PokeStar.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

            for (int a = 0; a < lista_ataques.size(); a++) {
                try {
                    lista_ataques.get(a).interrupt();
                    lista_ataques.get(a).join();
                } catch (InterruptedException ex) {
                    Logger.getLogger(PokeStar.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            ligado = false;
        }

        /**
         * Método que "Liga" o Jogo e mantém o jogo ligado.
         * Thread actualizar_mapa vai enviar o mapa actual para os jogadores a jogar o jogo.
         * Thread jogo verifica se o jogo ainda está a decorrer verificando o numero de jogadores ainda a jogar e se são Humanos.
         * Caso só haja um jogador a jogar envia uma mensagem de Vitória para esse jogador, caso seja Humano.
         * Quando só existirem Bots a jogar o jogo termina sem vencedores.
         */
        public void run() {
            ligado = true;
            lista_jogos.remove(this);
            m = new Mapa();
            Alive mapear = new Alive(m.getMatriz(), jogadores);
            mapear.start();
            for (int j = 0; j < jogadores.size(); j++) {
                try {
                    jogadores.get(j).getLP().clear();
                    String Dono = jogadores.get(j).getNick();
                    jogadores.get(j).getLP().add(new Treinador(jogadores.get(j).getNick(), "T" + j));
                    lt.add((Treinador) jogadores.get(j).getLP().getFirst());
                    if (j == 0) {
                        jogadores.get(j).getLP().add(new Pokemon("Squirtle", TipoPokemon.AGUA, Dono));
                        jogadores.get(j).getLP().add(new Pokemon("Bulbasaur", TipoPokemon.ERVA, Dono));
                        jogadores.get(j).getLP().add(new Pokemon("Charmander", TipoPokemon.FOGO, Dono));
                    } else if (j == 1) {
                        jogadores.get(j).getLP().add(new Pokemon("Totodile", TipoPokemon.AGUA, Dono));
                        jogadores.get(j).getLP().add(new Pokemon("Chikorita", TipoPokemon.ERVA, Dono));
                        jogadores.get(j).getLP().add(new Pokemon("Cyndaquil", TipoPokemon.FOGO, Dono));
                    } else if (j == 2) {
                        jogadores.get(j).getLP().add(new Pokemon("Mudkip", TipoPokemon.AGUA, Dono));
                        jogadores.get(j).getLP().add(new Pokemon("Treecko", TipoPokemon.ERVA, Dono));
                        jogadores.get(j).getLP().add(new Pokemon("Torchic", TipoPokemon.FOGO, Dono));
                    } else if (j == 3) {
                        jogadores.get(j).getLP().add(new Pokemon("Piplup", TipoPokemon.AGUA, Dono));
                        jogadores.get(j).getLP().add(new Pokemon("Turtwig", TipoPokemon.ERVA, Dono));
                        jogadores.get(j).getLP().add(new Pokemon("Chimchar", TipoPokemon.FOGO, Dono));
                    }
                    m.adiciona(j, jogadores.get(j).getLP());

                    // Criar Thread para verificar se o treinador do jogador morreu
                    // Se o treinador morrer o jogador é "expulso" do jogo

                    for (int p = 0; p < jogadores.get(j).getLP().size(); p++) {
                        Ataque a = new Ataque(actualizacao, m, jogadores.get(j).getLP().get(p));
                        lista_ataques.add(a);
                        a.start();
                    }

                    CMapa cm = new CMapa(m);
                    if (jogadores.get(j).getIA().equals("Humano")) {
                        jogadores.get(j).getOut().reset();
                        jogadores.get(j).getOut().writeObject(cm);
                        jogadores.get(j).getOut().flush();
                    }
                } catch (IOException ex) {
                    Logger.getLogger(PokeStar.class.getName()).log(Level.SEVERE, null, ex);
                }
            }



            for (int j = 0; j < jogadores.size(); j++) {
                if (jogadores.get(j).getIA().equals("Humano")) {
                    try {
                        jogadores.get(j).getOut().writeObject(new Chat("Servidor", "O Jogo Começou!"));
                        jogadores.get(j).getOut().flush();
                    } catch (IOException ex) {
                        Logger.getLogger(PokeStar.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }

            final Thread actualiza_mapa = new Thread() {

                public void run() {
                    try {
                        while (!interrupted() && jogadores.size() > 1) {
                            CMapa cm = new CMapa(m);

                            for (int j = 0; j < jogadores.size(); j++) {
                                if (jogadores.get(j).eHumano()) {
                                    try {
                                        jogadores.get(j).getOut().reset();
                                        jogadores.get(j).getOut().writeObject(cm);
                                        jogadores.get(j).getOut().flush();
                                    } catch (IOException e) {
                                    }
                                }
                            }
                            sleep(actualizacao);
                        }
                    } catch (InterruptedException ex) {
                    }
                }
            };

            Thread jogo = new Thread() {

                public void run() {
                    boolean jogoAlive = true;

                    while (!interrupted()) {
                        jogoAlive = false;

                        int jogadores_on = 0;
                        for (int j = 0; j < jogadores.size(); j++) {
                            if (((Treinador) jogadores.get(j).getLP().getFirst()).estaVivo()) {
                                jogadores_on++;
                                if (jogadores.get(j).eHumano()) {
                                    jogoAlive = true;
                                }
                            }
                        }

                        if (jogadores_on == 1) {
                            jogoAlive = false;
                        }
                        //System.out.println("JO: "+jogadores_on+" Alive "+jogoAlive);
                        //for(int i=0;i<jogadores.size();i++)
                        //  System.out.println(jogadores.get(i).getNick()+" "+jogadores.get(i).getLP().getFirst().getTipo()+" "+((Treinador)jogadores.get(i).getLP().getFirst()).estaVivo());
                        if (!jogoAlive) {
                            this.interrupt();
                        }

                    }
                    actualiza_mapa.interrupt();
                    Fim();

                    for (int i = 0; i < jogadores.size(); i++) {
                        if (((Treinador) jogadores.get(i).getLP().getFirst()).estaVivo()) {
                            if (jogadores.get(i).eHumano()) {
                                try {
                                    jogadores.get(i).getOut().writeObject(new Logout("GANHASTE!!!"));
                                } catch (IOException ex) {
                                    Logger.getLogger(PokeStar.class.getName()).log(Level.SEVERE, null, ex);
                                }
                            }
                        }
                    }
                }
            };

            jogo.start();
            actualiza_mapa.start();
        }

        /**
         * Método que executa uma acção quando o botão esquerdo é clicado.
         * 
         * @param c - Recebe a informação de um clique
         * @param j - Recebe o jogador que originou o clique
         */
        private void clique_esq(Clique c, Jogador j) {
            try {
                int y = c.getMe().getX() / (m.getX() / m.getMatriz().length);
                int x = c.getMe().getY() / (m.getY() / m.getMatriz()[0].length);
                j.setLastP(new Point(x, y));
                Objecto O = m.getMatriz()[x][y];
                j.setSelect(null);
                Info I = null;
                if (O.getTipo() == TipoObjecto.TREINADOR) {
                    Treinador T = (Treinador) O;
                    for (int p = 0; p < j.getLP().size(); p++) {
                        if (T == j.getLP().get(p)) {
                            j.setSelect(T);
                        }
                    }

                    I = new Info(T);
                } else if (m.getMatriz()[x][y].getTipo() == TipoObjecto.VAZIO) {
                    Vazio V = (Vazio) O;
                    I = new Info(V);
                } else if (m.getMatriz()[x][y].getTipo() == TipoObjecto.OBSTACULO) {
                    Obstaculo Ob = (Obstaculo) O;
                    I = new Info(Ob);
                } else if (m.getMatriz()[x][y].getTipo() == TipoObjecto.POKEMON) {
                    Pokemon P = (Pokemon) O;
                    for (int p = 0; p < j.getLP().size(); p++) {
                        if (P == j.getLP().get(p)) {
                            j.setSelect(P);
                        }
                    }
                    I = new Info(P);
                }
                j.getOut().reset();
                j.getOut().writeObject(I);
                j.getOut().flush();
            } catch (IOException ex) {
                Logger.getLogger(PokeStar.class.getName()).log(Level.SEVERE, null, ex);
            } catch (ArrayIndexOutOfBoundsException ex) {
            }
        }

        /**
         * Método que executa uma acção quando o botão direito é clicado.
         * 
         * @param c - Recebe a informação de um clique
         * @param j - Recebe o jogador que originou o clique
         */
        private void clique_dir(Clique c, Jogador j) {
            if (j.getSelect() != null) {
                int y = c.getMe().getX() / (m.getX() / m.getMatriz().length);
                int x = c.getMe().getY() / (m.getY() / m.getMatriz()[0].length);
                Point p = new Point(x, y);
                if (m.getMatriz()[x][y].getTipo() == TipoObjecto.VAZIO) {
                    Ordem o = new Ordem(m.getMatriz()[x][y], j, j.getLastP(), p, j.getSelect(), this);
                    o.start();
                } else if (m.getMatriz()[x][y].getTipo() == TipoObjecto.POKEMON) {
                    Ordem o = new Ordem(m.getMatriz()[x][y], j, j.getLastP(), p, j.getSelect(), this);
                    o.start();
                } else if (m.getMatriz()[x][y].getTipo() == TipoObjecto.TREINADOR) {
                    Ordem o = new Ordem(m.getMatriz()[x][y], j, j.getLastP(), p, j.getSelect(), this);
                    o.start();
                }
            }
        }
    }

    /**
     * Classe Jogador que gere um Jogador ligado e autenticado no servidor
     */
    public class Jogador extends Thread {

        private String Nick;
        private Socket socket;
        private ObjectInputStream ois;
        private ObjectOutputStream oos;
        private Jogo J;
        private String IA;
        private LinkedList<Objecto> personagens = new LinkedList<Objecto>();
        private boolean aJogar = false;
        private Objecto Select = null;
        private Point lastP;
        private boolean ligado = false;

        /**
         * Constructor do Jogador Humano
         * 
         * @param s - Recebe o socket que liga o servidor ao cliente.
         * @param i - Recebe o canal de input que servirá para receber informação do cliente.
         * @param o - Recebe o canal de output que servirá para enviar informação para o cliente.
         */
        
        public Jogador(Socket s, ObjectInputStream i, ObjectOutputStream o) {
            this.socket = s;
            this.ois = i;
            this.oos = o;
            this.IA = "Humano";
        }

        /**
         * Constructor para o Jogador CPU
         * @param nome - Nome fícticio para um Jogador CPU
         * @param dificuldade - Dificuldade do jogador CPU
         */
  
        private Jogador(String nome, String dificuldade) {
            this.Nick = nome;
            this.IA = dificuldade;
        }

        /**
         * 
         * @return Retorna false caso não esteja a jogar e true caso esteja a jogar
         */
        public boolean getStatus() {
            return aJogar;
        }

        /**
         * Método que altera o Estado do Jogador.
         * @param b - Recebe true ou false para alterar o estado do jogador.
         */
        public void setStatus(boolean b) {
            aJogar = b;
        }

        /**
         * 
         * @return Retorna o Objecto que o Jogador tem seleccionado
         */
        public Objecto getSelect() {
            return Select;
        }

        /**
         * Método que altera o objecto que o jogador tem seleccionado 
         * @param s - Recebe um Objecto
         */
        public void setSelect(Objecto s) {
            this.Select = s;
        }

        /**
         * 
         * @return Retorna o canal de output do cliente.
         */
        public ObjectOutputStream getOut() {
            return oos;
        }

        /**
         * 
         * @return Retorna a Dificuldade do Jogador CPU
         */
        public String getIA() {
            return IA;
        }

        /**
         * 
         * @return Retorna true se o jogador for Humano ou false caso o jogador seja CPU
         */
        public boolean eHumano() {
            if (getIA().equals("Humano")) {
                return true;
            }
            return false;
        }

        /**
         * 
         * @return Retorna o nick do Jogador
         */
        public String getNick() {
            return Nick;
        }

        /**
         * 
         * @return Retorna a lista de personagens que o jogador tem.
         */
        public LinkedList<Objecto> getLP() {
            return personagens;
        }

        /**
         * 
         * @return Retorna a posição do ultimo clique dado pelo jogador.
         */
        public Point getLastP() {
            return lastP;
        }

        /**
         * Método que altera a posição do ultimo clique dado pelo jogador.
         * @param P - Recebe um ponto
         */
        public void setLastP(Point P) {
            lastP = P;
        }

        /**
         * Método que gere os inputs dados pelo Jogador
         */
        public void run() {
            if (eHumano()) {
                ligado = true;
                try {
                    boolean vl = false;
                    while (!vl) {
                        Comunicacao M = (Comunicacao) ois.readObject();
                        switch (M.getTipo()) {
                            case LOGIN:
                                Login L = (Login) M;
                                Nick = L.getUser();
                                Confirmacao lv = VerificarLogin(L);
                                oos.reset();
                                oos.writeObject(lv);
                                oos.flush();
                                vl = lv.getResposta();
                                if (vl) {
                                    lista_ligados.add(this);
                                }

                                break;
                            case REGISTO:
                                Registo R = (Registo) M;

                                Confirmacao CR = VerificarRegisto(R);
                                oos.reset();
                                oos.writeObject(CR);
                                oos.flush();

                                if (CR.getResposta()) {
                                    lista_registados.add(R.getUser() + " " + R.getPass());
                                    ReloadUsers();
                                }
                                break;
                            default:
                                break;
                        }
                    }
                    // JÁ DENTRO DO JOGO!!!!
                    while (ligado) {
                        do {
                            aJogar = false;
                            Comunicacao U = (Comunicacao) ois.readObject();
                            //Esperar por um Pedido por Parte do Jogador

                            switch (U.getTipo()) {
                                case UPDATE:
                                    LinkedList<String> r = new LinkedList<String>();
                                    for (int j = 0; j < lista_jogos.size(); j++) {
                                        // Nome + Jogadores Actuais / Jogadores Totais + Criador
                                        r.add(lista_jogos.get(j).getNome() + " " + (lista_jogos.get(j).getJMax() - lista_jogos.get(j).getJogadores().size()) + " " + lista_jogos.get(j).getCriador().getNick());
                                    }
                                    // Enviar Informação Acerca dos Jogos Em Curso
                                    Rooms R = new Rooms(r);
                                    oos.reset();
                                    oos.writeObject(R);
                                    oos.flush();
                                    break;
                                case CRIAR:
                                    boolean existe = false;
                                    CriarJogo CJ = (CriarJogo) U;
                                    for (int j = 0; j < lista_jogos.size(); j++) {
                                        if (CJ.getNome().equals(lista_jogos.get(j).getNome())) {
                                            existe = true;
                                        }
                                    }

                                    if (!existe) {
                                        aJogar = true;
                                        Confirmacao C = new Confirmacao(aJogar);
                                        oos.reset();
                                        oos.writeObject(C);
                                        oos.flush();
                                        this.J = new Jogo(CJ);
                                        J.setCriador(this);
                                        lista_jogos.add(J);
                                    } else {
                                        aJogar = false;
                                        Confirmacao C = new Confirmacao(aJogar, "Já Existe Um Jogo Com Esse Nome!");
                                        oos.reset();
                                        oos.writeObject(C);
                                        oos.flush();
                                    }
                                    break;
                                case JUNTAR:
                                    aJogar = false;
                                    JuntarJogo JJ = (JuntarJogo) U;
                                    for (int j = 0; j < lista_jogos.size(); j++) {
                                        if (lista_jogos.get(j).getNome().equals(JJ.getJogo())) {
                                            if (!lista_jogos.get(j).cheio()) {
                                                J = lista_jogos.get(j);
                                                J.addJogador(this);
                                                aJogar = true;
                                            }
                                        }
                                    }

                                    if (aJogar) {
                                        oos.reset();
                                        oos.writeObject(new Confirmacao(aJogar));
                                        oos.flush();
                                    } else {
                                        oos.reset();
                                        oos.writeObject(new Confirmacao(aJogar, "O Jogo Já Se Encontra Cheio!"));
                                        oos.flush();
                                    }

                                    // Aqui tambem "Entrou no Jogo"s
                                    break;
                                case LOGOFF:
                                    oos.close();
                                    ois.close();
                                    socket.close();
                                    lista_ligados.remove(this);
                                    ligado = false;
                                    this.interrupt();
                                    break;
                                default:
                                    break;
                            }
                        } while (!aJogar && ligado);

                        if (ligado) {
                            while (aJogar) {
                                Comunicacao C = (Comunicacao) ois.readObject();

                                if (C.getTipo() == TipoComunicacao.CHAT) {
                                    Chat chat = (Chat) C;

                                    for (int j = 0; j < J.getJogadores().size(); j++) {
                                        if (J.getJogadores().get(j).eHumano()) {
                                            J.getJogadores().get(j).getOut().reset();
                                            J.getJogadores().get(j).getOut().writeObject(chat);
                                            J.getJogadores().get(j).getOut().flush();
                                        }
                                    }

                                } else if (C.getTipo() == TipoComunicacao.CLIQUE) {
                                    Clique c = (Clique) C;

                                    if (c.getMe().getButton() == 1) {
                                        J.clique_esq(c, this);
                                    } else if (c.getMe().getButton() == 2) {
                                    } else if (c.getMe().getButton() == 3) {
                                        J.clique_dir(c, this);
                                    }
                                }
                            }
                            personagens.clear();
                            J.rmvJogador(this);
                            J = null;
                        }
                    }
                } catch (ClassNotFoundException ex) {
                    lista_ligados.remove(this);
                    //Logger.getLogger(PokeStar.class.getName()).log(Level.SEVERE, null, ex);
                } catch (IOException ex) {
                    lista_ligados.remove(this);
                    //Logger.getLogger(PokeStar.class.getName()).log(Level.SEVERE, null, ex);
                }
            } else {
                // SE É CONTROLADO POR PC
            }
        } // FIM RUN()
    }

    /**
     * Método que envia uma Confirmação a uma tentativa de login.
     * @param J - Recebe uma String com o Nick usado para tentativa de login.
     * @return Retorna uma Confirmação true ou false, dependente se o nick já se encontra ligado ao servidor.
     */
    private Confirmacao JogadorLigado(String J) {
        for (int i = 0; i < lista_ligados.size(); i++) {
            if (lista_ligados.get(i).getNick().equals(J)) {
                return new Confirmacao(false, "Jogador Já Ligado");
            }
        }
        return new Confirmacao(true);
    }

    /**
     * Método que confirma, ou não, o Login quando se compara o nick e a pass dados pelos jogadores com os do ficheiro do server.
     * @param L - Recebe uma Mensagem de Login
     * @return Retorna uma Confirmação true ou false caso o login seja feito com sucesso ou não.
     */
    public Confirmacao VerificarLogin(Login L) {
        for (int i = 0; i < lista_registados.size(); i++) {
            if (L.getUser().equals(lista_registados.get(i).split(" ")[0]) && L.getPass().equals(lista_registados.get(i).split(" ")[1])) {
                return JogadorLigado(L.getUser());
            }
        }
        return new Confirmacao(false, "Login Incorrecto");
    }

    /**
     * Método que Verifica o Registo de um novo jogador.
     * @param R - Recebe uma Mensagem de Registo.
     * @return Retorna uma Confirmação caso o Registo seja feito com sucesso ou não.
     */
    public Confirmacao VerificarRegisto(Registo R) {
        for (int i = 0; i < lista_registados.size(); i++) {
            if (R.getUser().equals(lista_registados.get(i).split(" ")[0])) {
                return new Confirmacao(false, "O Username Já Existe!");
            }
        }

        if (R.getUser().contains(" ") || R.getPass().contains(" ")) {
            return new Confirmacao(false, "Não É Possível Usar Espaços no Username nem na Password!");
        }

        return new Confirmacao(true);
    }

    /**
     * Método que Re-Lê o ficheiro de utilizadores registados no servidor.
     * @throws IOException 
     */
    public void ReloadUsers() throws IOException {
        File f = new File(file);
        FileWriter out = new FileWriter(f);
        String s = "";

        for (int i = 0; i < lista_registados.size(); i++) {
            s += lista_registados.get(i) + "\n";
        }
        out.write(s);
        out.close();
    }

    /**
     * Método que fica a aguardar ligações ao servidor
     */
    private class AguardarLigacoes extends Thread {

        public AguardarLigacoes() {
        }

        public void run() {
            try {
                sSocket = new ServerSocket(PORT);
                while (!interrupted()) {
                    Socket cSocket = sSocket.accept();
                    ObjectInputStream ois = new ObjectInputStream(cSocket.getInputStream());
                    ObjectOutputStream oos = new ObjectOutputStream(cSocket.getOutputStream());

                    Jogador J = new Jogador(cSocket, ois, oos);
                    J.start();
                }
            } catch (IOException ex) {
                //Logger.getLogger(PokeStar.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    /**
     * @param args the command line arguments
     */
    private final int PORT = 25000;
    private ServerSocket sSocket;
    private String file = "registados.txt";
    private LinkedList<String> lista_registados = new LinkedList<String>();
    private LinkedList<Jogador> lista_ligados = new LinkedList<Jogador>();
    private LinkedList<Jogo> lista_jogos = new LinkedList<Jogo>();

    public static void main(String[] args) {
        PokeStar PS = new PokeStar();
        PS.start();
    }

    /**
     * Método que inicia o servidor, lendo o ficheiro de utilizadores registados, apresentando o interface gráfico e ligando a thread aguardar ligaçoẽs.
     */
    private void start() {
        ler_users(file);

        graph();

        AguardarLigacoes AL = new AguardarLigacoes();
        AL.start();
    }

    /**
     * Método que cria e apresenta o interface gráfico
     */
    private void graph() {
        int t = 200;
        final JFrame janela = new JFrame("Servidor");
        JButton sair = new JButton("Desligar");
        JLabel label = new JLabel("Servidor Online");
        janela.setSize(t, t);

        Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
        int location_Y = (int) (dim.getHeight() / 2 - t / 2);
        int location_X = (int) (dim.getWidth() / 2 - t / 2);

        janela.setLocation(location_X, location_Y);

        Container c_janela = janela.getContentPane();
        c_janela.setLayout(new BorderLayout());
        c_janela.add(label, BorderLayout.CENTER);
        c_janela.add(sair, BorderLayout.CENTER);

        sair.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent ae) {
                try {
                    for (int i = 0; i < lista_ligados.size(); i++) {
                        try {
                            lista_ligados.get(i).socket.close();
                            lista_ligados.get(i).ois.close();
                            lista_ligados.get(i).oos.close();
                        } catch (IOException ex) {
                            Logger.getLogger(PokeStar.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                    sSocket.close();

                } catch (IOException ex) {
                    Logger.getLogger(PokeStar.class.getName()).log(Level.SEVERE, null, ex);
                }
                janela.dispose();
                System.exit(0);
            }
        });
        janela.setVisible(true);
    }

    /**
     * Método que lê um ficheiro dado e converte para nick-password para que os jogadores se possam autenticar no servidor.
     * Caso o ficheiro não exista é criado um ficheiro em branco pronto para receber novos registos
     * @param file - Recebe um caminho para um ficheiro onde constam os jogadores registados no servidor.
     */
    private void ler_users(String file) {
        String c;
        File f = null;
        FileReader in = null;
        BufferedReader bf = null;
        try {
            f = new File(file);

            if (!f.exists()) {
                f.createNewFile();
            } else {
                in = new FileReader(f);
                bf = new BufferedReader(in);
            }

            while ((c = bf.readLine()) != null) {
                lista_registados.add(c);
            }
            bf.close();
            in.close();
        } catch (IOException ex) {
            Logger.getLogger(PokeStar.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NullPointerException ex){
            ler_users(file);
        }
    }
}